//
// Ephi - simulation of magnetic fields and particles
// Copyright (C) 2007 Indrek Mandre <indrek(at)mare.ee>
// For more information please see http://www.mare.ee/indrek/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

#include <stdarg.h>
#include <assert.h>

#include <algorithm>

#include "ephi.hpp"

void Config::reg_num (const char *group, const char *name, prec_t dflt)
{
  assert(types.find(name) == types.end());
  type_t& t = types.insert (types_t::value_type(name, type_t())).first->second;
  t.t = type_t::PREC;
  t.name = name;
  t.group = &groups[group];
  t.dflt.t = &t;
  t.dflt.value_prec = dflt;
}

void Config::reg_vect3d (const char *group, const char *name, const vect3d& dflt)
{
  assert(types.find(name) == types.end());
  type_t& t = types.insert (types_t::value_type(name, type_t())).first->second;
  t.t = type_t::VECT3D;
  t.name = name;
  t.group = &groups[group];
  t.dflt.t = &t;
  t.dflt.value_vect3d = dflt;
}

void Config::reg_bool (const char *group, const char *name, bool dflt)
{
  assert(types.find(name) == types.end());
  type_t& t = types.insert (types_t::value_type(name, type_t())).first->second;
  t.t = type_t::BOOL;
  t.name = name;
  t.group = &groups[group];
  t.dflt.t = &t;
  t.dflt.value_bool = dflt;
}

void Config::reg_enum (const char *group, const char *name, const char *dflt, ...)
{
  assert(types.find(name) == types.end());
  type_t& t = types.insert (types_t::value_type(name, type_t())).first->second;
  t.t = type_t::ENUM;
  t.name = name;
  t.group = &groups[group];
  t.dflt.t = &t;
  t.dflt.value_enum = dflt;

  va_list vl;
  va_start (vl, dflt);
  while (1)
    {
      const char *str = va_arg (vl, const char *);
      if ( !str )
          break;
      t.enums.push_back (str);
    }
  va_end (vl);
  assert(std::find (t.enums.begin(), t.enums.end(), t.dflt.value_enum) != t.enums.end());
}

void Config::reg_str (const char *group, const char *name, const char *dflt)
{
  assert(types.find(name) == types.end());
  type_t& t = types.insert (types_t::value_type(name, type_t())).first->second;
  t.t = type_t::STR;
  t.name = name;
  t.group = &groups[group];
  t.dflt.t = &t;
  t.dflt.value_str = dflt;
}

bool Config::select_group (const char *name, size_t index)
{
  groups_t::iterator fit = groups.find (name);
  if ( fit == groups.end() )
      return false;
  if ( fit->second.instances.size() <= index )
      return false;
  fit->second.ref = &fit->second.instances[index];
  return true;
}

const char * Config::get_str (const char *name)
{
  value_t& v = get_value (name, type_t::STR);
  return v.value_str.c_str();
}

const char * Config::get_enum (const char *name)
{
  value_t& v = get_value (name, type_t::ENUM);
  return v.value_enum.c_str();
}

prec_t Config::get_prec (const char *name)
{
  value_t& v = get_value (name, type_t::PREC);
  return v.value_prec;
}

size_t Config::get_size (const char *name)
{
  value_t& v = get_value (name, type_t::PREC);
  return (size_t)prec2double(v.value_prec);
}

vect3d& Config::get_vect3d (const char *name, vect3d& res)
{
  value_t& v = get_value (name, type_t::VECT3D);
  res = v.value_vect3d;
  return res;
}

bool Config::get_bool (const char *name)
{
  value_t& v = get_value (name, type_t::BOOL);
  return v.value_bool;
}

static bool error (FILE *fp, const char *fmt, ...)
{
  va_list va;
  va_start (va, fmt);
  Ephi::debug (Ephi::DEBUG_ERROR, fmt, va);
  va_end (va);
  fclose (fp);
  return false;
}

bool Config::load (const char *path)
{
  FILE *fp = fopen (path, "r");
  if ( !fp )
      return false;
  size_t lineno = 0;
  while ( !feof(fp) )
    {
      char line[1024];
      fgets (line, sizeof(line), fp);
      lineno++;
      char *s = line;
      while ( isspace (*s) )
          s++;
      if ( !*s || *s == '#' ) // empty line or a comment
          continue;
      if ( *s == '[' ) // group selector
        {
          s++;
          char *n = strchr (s, ']');
          if ( !n )
              return error (fp, "%lu: syntax error\n", lineno);
          *n = 0;
          groups_t::iterator fit = groups.find (s);
          if ( fit == groups.end() )
            {
              Ephi::debug (Ephi::DEBUG_ERROR, "%lu: unknown group '%s'\n", lineno, s);
              continue;
            }
          fit->second.instances.push_back (values_t());
          continue;
        }
      char *key = s;
      while ( *s && *s != '=' && !isspace (*s) ) s++;
      if ( !*s )
          return error (fp, "%lu: syntax error: = not found\n", lineno);
      *s = 0;
      s++;
      while ( *s && isspace (*s) ) s++;
      if ( *s != '=' )
          return error (fp, "%lu: syntax error: = not found\n", lineno);
      s++;
      while ( *s && isspace (*s) ) s++;
      types_t::iterator it = types.find (key);
      if ( it == types.end() )
        {
          Ephi::debug (Ephi::DEBUG_ERROR, "%lu: unknown key: '%s'\n", lineno, key);
          continue;
        }
      type_t& t = it->second;
      char *e = s;
      while ( *e ) e++;
      e--;
      while ( isspace (*e) && e != s )
        {
          *e = 0;
          e--;
        }
      if ( t.t != type_t::STR )
        {
          char *comment = strchr (s, '#');
          if ( comment )
              *comment = 0;
        }
      group_t *g = t.group;
      if ( g->instances.empty() )
          g->instances.push_back (values_t());
      values_t& vals = g->instances.back();
      if ( vals.find (key) != vals.end() )
          return error (fp, "%lu: redefinition of '%s'\n", lineno, key);
      value_t &v = vals[key];
      v.t = &t;
      switch (t.t)
        {
          case type_t::PREC: {
            v.value_prec = atof (s);
            break;
          }
          case type_t::BOOL: {
            if ( strcmp (s, "true") == 0 )
              {
                v.value_bool = true;
              }
            else if ( strcmp (s, "false") == 0 )
              {
                v.value_bool = false;
              }
            else
              {
                Ephi::debug (Ephi::DEBUG_ERROR, "%lu: key '%s' is bool, must be true or false\n", lineno, key);
                fclose (fp);
                return false;
              }
            v.value_prec = atof (s);
            break;
          }
          case type_t::STR: {
            v.value_str = s;
            break;
          }
          case type_t::ENUM: {
            v.value_enum = s;
            if ( std::find (t.enums.begin(), t.enums.end(), v.value_enum) == t.enums.end() )
                return error (fp, "%lu: %s: unknown enumeration value '%s'\n", lineno, key, s);
            break;
          }
          case type_t::VECT3D: {
            if ( *s != '(' ) return error (fp, "%lu: %s: expected '('\n", lineno, key);
            s++;
            while ( *s && isspace (*s) ) s++;
            if ( !*s ) return error (fp, "%lu: %s: expected vector (1)\n", lineno, key);
            double dx, dy, dz;
            if ( sscanf (s, "%lf", &dx) != 1 ) return error (fp, "%lu: %s: expected vector(2)\n", lineno, key);
            while ( *s && !isspace (*s) && *s != ',' ) s++;
            while ( *s && isspace (*s) ) s++;
            if ( *s != ',' ) return error (fp, "%lu: %s: expected vector(3)\n", lineno, key);
            s++;
            while ( *s && isspace (*s) ) s++;
            if ( sscanf (s, "%lf", &dy) != 1 ) return error (fp, "%lu: %s: expected vector(4)\n", lineno, key);
            while ( *s && !isspace (*s) && *s != ',' ) s++;
            while ( *s && isspace (*s) ) s++;
            if ( *s != ',' ) return error (fp, "%lu: %s: expected vector(5)\n", lineno, key);
            s++;
            while ( *s && isspace (*s) ) s++;
            if ( sscanf (s, "%lf", &dz) != 1 ) return error (fp, "%lu: %s: expected vector(6)\n", lineno, key);
            while ( *s && !isspace (*s) && *s != ',' && *s != ')' ) s++;
            while ( *s && isspace (*s) ) s++;
            if ( *s != ')' ) return error (fp, "%lu: %s: expected vector(7)\n", lineno, key);
            s++;
            while ( *s && isspace (*s) ) s++;
            if ( *s ) return error (fp, "%lu: %s: expected vector(8)\n", lineno, key);
            v.value_vect3d.x = dx;
            v.value_vect3d.y = dy;
            v.value_vect3d.z = dz;
            break;
          }
        }
    }
  fclose (fp);
  return true;
}

Config::value_t& Config::get_value (const char *name, type_t::et t)
{
  value_t *v = 0;
  types_t::iterator fit = types.find (name);
  if ( fit == types.end() )
    {
      Ephi::debug (Ephi::DEBUG_CRITICAL, "Config::get_value: unknown key '%s'\n", name);
      abort();
    }
  type_t& type = fit->second;
  v = &type.dflt;
  if ( fit->second.t != t )
    {
      Ephi::debug (Ephi::DEBUG_CRITICAL, "Config::get_value: expected type %u, got %u\n", fit->second.t, t);
      abort();
    }
  group_t& g = *type.group;
  if ( !g.ref && !g.instances.empty() )
      g.ref = &g.instances.front();
  if ( g.ref )
    {
      values_t::iterator sit = g.ref->find (name);
      if ( sit != g.ref->end() )
          v = &sit->second;
    }
  return *v;
}

